<html><head><meta charset="utf-8"/><title>创建对象</title></head><body><h1>创建对象</h1><div class="x-wiki-content x-main-content">
 <p>
  JavaScript对每个创建的对象都会设置一个原型，指向它的原型对象。
 </p>
 <p>
  当我们用
  <code>
   obj.xxx
  </code>
  访问一个对象的属性时，JavaScript引擎先在当前对象上查找该属性，如果没有找到，就到其原型对象上找，如果还没有找到，就一直上溯到
  <code>
   Object.prototype
  </code>
  对象，最后，如果还没有找到，就只能返回
  <code>
   undefined
  </code>
  。
 </p>
 <p>
  例如，创建一个
  <code>
   Array
  </code>
  对象：
 </p>
 <pre><code>var arr = [1, 2, 3];
</code></pre>
 <p>
  其原型链是：
 </p>
 <pre><code>arr ----&gt; Array.prototype ----&gt; Object.prototype ----&gt; null
</code></pre>
 <p>
  <code>
   Array.prototype
  </code>
  定义了
  <code>
   indexOf()
  </code>
  、
  <code>
   shift()
  </code>
  等方法，因此你可以在所有的
  <code>
   Array
  </code>
  对象上直接调用这些方法。
 </p>
 <p>
  当我们创建一个函数时：
 </p>
 <pre><code>function foo() {
    return 0;
}
</code></pre>
 <p>
  函数也是一个对象，它的原型链是：
 </p>
 <pre><code>foo ----&gt; Function.prototype ----&gt; Object.prototype ----&gt; null
</code></pre>
 <p>
  由于
  <code>
   Function.prototype
  </code>
  定义了
  <code>
   apply()
  </code>
  等方法，因此，所有函数都可以调用
  <code>
   apply()
  </code>
  方法。
 </p>
 <p>
  很容易想到，如果原型链很长，那么访问一个对象的属性就会因为花更多的时间查找而变得更慢，因此要注意不要把原型链搞得太长。
 </p>
 <h3>
  构造函数
 </h3>
 <p>
  除了直接用
  <code>
   { ... }
  </code>
  创建一个对象外，JavaScript还可以用一种构造函数的方法来创建对象。它的用法是，先定义一个构造函数：
 </p>
 <pre><code>function Student(name) {
    this.name = name;
    this.hello = function () {
        alert('Hello, ' + this.name + '!');
    }
}
</code></pre>
 <p>
  你会问，咦，这不是一个普通函数吗？
 </p>
 <p>
  这确实是一个普通函数，但是在JavaScript中，可以用关键字
  <code>
   new
  </code>
  来调用这个函数，并返回一个对象：
 </p>
 <pre><code>var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!
</code></pre>
 <p>
  <em>
   注意
  </em>
  ，如果不写
  <code>
   new
  </code>
  ，这就是一个普通函数，它返回
  <code>
   undefined
  </code>
  。但是，如果写了
  <code>
   new
  </code>
  ，它就变成了一个构造函数，它绑定的
  <code>
   this
  </code>
  指向新创建的对象，并默认返回
  <code>
   this
  </code>
  ，也就是说，不需要在最后写
  <code>
   return this;
  </code>
  。
 </p>
 <p>
  新创建的
  <code>
   xiaoming
  </code>
  的原型链是：
 </p>
 <pre><code>xiaoming ----&gt; Student.prototype ----&gt; Object.prototype ----&gt; null
</code></pre>
 <p>
  也就是说，
  <code>
   xiaoming
  </code>
  的原型指向函数
  <code>
   Student
  </code>
  的原型。如果你又创建了
  <code>
   xiaohong
  </code>
  、
  <code>
   xiaojun
  </code>
  ，那么这些对象的原型与
  <code>
   xiaoming
  </code>
  是一样的：
 </p>
 <pre><code>xiaoming ↘
xiaohong -→ Student.prototype ----&gt; Object.prototype ----&gt; null
xiaojun  ↗
</code></pre>
 <p>
  用
  <code>
   new Student()
  </code>
  创建的对象还从原型上获得了一个
  <code>
   constructor
  </code>
  属性，它指向函数
  <code>
   Student
  </code>
  本身：
 </p>
 <pre><code>xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true

Object.getPrototypeOf(xiaoming) === Student.prototype; // true

xiaoming instanceof Student; // true
</code></pre>
 <p>
  看晕了吧？用一张图来表示这些乱七八糟的关系就是：
 </p>
 <p>
  <img alt="protos" data-src="/files/attachments/1024698721053600/l" src="https://www.liaoxuefeng.com/files/attachments/1024698721053600/l"/>
 </p>
 <p>
  红色箭头是原型链。注意，
  <code>
   Student.prototype
  </code>
  指向的对象就是
  <code>
   xiaoming
  </code>
  、
  <code>
   xiaohong
  </code>
  的原型对象，这个原型对象自己还有个属性
  <code>
   constructor
  </code>
  ，指向
  <code>
   Student
  </code>
  函数本身。
 </p>
 <p>
  另外，函数
  <code>
   Student
  </code>
  恰好有个属性
  <code>
   prototype
  </code>
  指向
  <code>
   xiaoming
  </code>
  、
  <code>
   xiaohong
  </code>
  的原型对象，但是
  <code>
   xiaoming
  </code>
  、
  <code>
   xiaohong
  </code>
  这些对象可没有
  <code>
   prototype
  </code>
  这个属性，不过可以用
  <code>
   __proto__
  </code>
  这个非标准用法来查看。
 </p>
 <p>
  现在我们就认为
  <code>
   xiaoming
  </code>
  、
  <code>
   xiaohong
  </code>
  这些对象“继承”自
  <code>
   Student
  </code>
  。
 </p>
 <p>
  不过还有一个小问题，注意观察：
 </p>
 <pre><code>xiaoming.name; // '小明'
xiaohong.name; // '小红'
xiaoming.hello; // function: Student.hello()
xiaohong.hello; // function: Student.hello()
xiaoming.hello === xiaohong.hello; // false
</code></pre>
 <p>
  <code>
   xiaoming
  </code>
  和
  <code>
   xiaohong
  </code>
  各自的
  <code>
   name
  </code>
  不同，这是对的，否则我们无法区分谁是谁了。
 </p>
 <p>
  <code>
   xiaoming
  </code>
  和
  <code>
   xiaohong
  </code>
  各自的
  <code>
   hello
  </code>
  是一个函数，但它们是两个不同的函数，虽然函数名称和代码都是相同的！
 </p>
 <p>
  如果我们通过
  <code>
   new Student()
  </code>
  创建了很多对象，这些对象的
  <code>
   hello
  </code>
  函数实际上只需要共享同一个函数就可以了，这样可以节省很多内存。
 </p>
 <p>
  要让创建的对象共享一个
  <code>
   hello
  </code>
  函数，根据对象的属性查找原则，我们只要把
  <code>
   hello
  </code>
  函数移动到
  <code>
   xiaoming
  </code>
  、
  <code>
   xiaohong
  </code>
  这些对象共同的原型上就可以了，也就是
  <code>
   Student.prototype
  </code>
  ：
 </p>
 <p>
  <img alt="protos2" data-src="/files/attachments/1024700039819712/l" src="https://www.liaoxuefeng.com/files/attachments/1024700039819712/l"/>
 </p>
 <p>
  修改代码如下：
 </p>
 <pre><code>function Student(name) {
    this.name = name;
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
};
</code></pre>
 <p>
  用
  <code>
   new
  </code>
  创建基于原型的JavaScript的对象就是这么简单！
 </p>
 <h3>
  忘记写new怎么办
 </h3>
 <p>
  如果一个函数被定义为用于创建对象的构造函数，但是调用时忘记了写
  <code>
   new
  </code>
  怎么办？
 </p>
 <p>
  在strict模式下，
  <code>
   this.name = name
  </code>
  将报错，因为
  <code>
   this
  </code>
  绑定为
  <code>
   undefined
  </code>
  ，在非strict模式下，
  <code>
   this.name = name
  </code>
  不报错，因为
  <code>
   this
  </code>
  绑定为
  <code>
   window
  </code>
  ，于是无意间创建了全局变量
  <code>
   name
  </code>
  ，并且返回
  <code>
   undefined
  </code>
  ，这个结果更糟糕。
 </p>
 <p>
  所以，调用构造函数千万不要忘记写
  <code>
   new
  </code>
  。为了区分普通函数和构造函数，按照约定，构造函数首字母应当大写，而普通函数首字母应当小写，这样，一些语法检查工具如
  <a href="http://www.jslint.com/">
   jslint
  </a>
  将可以帮你检测到漏写的
  <code>
   new
  </code>
  。
 </p>
 <p>
  最后，我们还可以编写一个
  <code>
   createStudent()
  </code>
  函数，在内部封装所有的
  <code>
   new
  </code>
  操作。一个常用的编程模式像这样：
 </p>
 <pre><code>function Student(props) {
    this.name = props.name || '匿名'; // 默认值为'匿名'
    this.grade = props.grade || 1; // 默认值为1
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
};

function createStudent(props) {
    return new Student(props || {})
}
</code></pre>
 <p>
  这个
  <code>
   createStudent()
  </code>
  函数有几个巨大的优点：一是不需要
  <code>
   new
  </code>
  来调用，二是参数非常灵活，可以不传，也可以这么传：
 </p>
 <pre><code>var xiaoming = createStudent({
    name: '小明'
});

xiaoming.grade; // 1
</code></pre>
 <p>
  如果创建的对象有很多属性，我们只需要传递需要的某些属性，剩下的属性可以用默认值。由于参数是一个Object，我们无需记忆参数的顺序。如果恰好从
  <code>
   JSON
  </code>
  拿到了一个对象，就可以直接创建出
  <code>
   xiaoming
  </code>
  。
 </p>
 <h3>
  练习
 </h3>
 <p>
  请利用构造函数定义
  <code>
   Cat
  </code>
  ，并让所有的Cat对象有一个
  <code>
   name
  </code>
  属性，并共享一个方法
  <code>
   say()
  </code>
  ，返回字符串
  <code>
   'Hello, xxx!'
  </code>
  ：
 </p>
 <pre><code class="language-x-javascript">'use strict';
----
function Cat(name) {
    //
}
----
// 测试:
var kitty = new Cat('Kitty');
var doraemon = new Cat('哆啦A梦');
if (kitty &amp;&amp; kitty.name === 'Kitty' &amp;&amp; kitty.say &amp;&amp; typeof kitty.say === 'function' &amp;&amp; kitty.say() === 'Hello, Kitty!' &amp;&amp; kitty.say === doraemon.say) {
    console.log('测试通过!');
} else {
    console.log('测试失败!');
}
</code></pre>
</div>
</body></html>